package com.demo1;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Emulator;
import com.github.unidbg.Module;
import com.github.unidbg.ModuleListener;
import com.github.unidbg.arm.backend.Backend;
import com.github.unidbg.arm.backend.CodeHook;
import com.github.unidbg.arm.backend.UnHook;
import com.github.unidbg.arm.backend.Unicorn2Factory;
import com.github.unidbg.file.FileResult;
import com.github.unidbg.file.IOResolver;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.linux.android.dvm.array.ByteArray;
import com.github.unidbg.linux.file.ByteArrayFileIO;
import com.github.unidbg.memory.Memory;
import com.utils.SearchRc4;
import okhttp3.Headers;
import okhttp3.HttpUrl;
import okhttp3.Request;
import okhttp3.RequestBody;
import okio.Buffer;
import okio.BufferedSink;
import org.apache.commons.codec.binary.Base64;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

public class XYASS extends AbstractJni implements IOResolver {
    private final AndroidEmulator emulator;
    private final VM vm;
    public DvmClass XhsHttpInterceptor;
    public DvmObject<?> xhshttpinterceptorObject;
    public long cPtr;
    private Request request;
    private String url;
    public long count = 0;


    XYASS() {
        // 创建模拟器实例
        emulator = AndroidEmulatorBuilder.
                for32Bit().
                addBackendFactory(new Unicorn2Factory(true)).
                build();

        // 开启多线程
        emulator.getBackend().registerEmuCountHook(10000); // 设置执行多少条指令切换一次线程
        emulator.getSyscallHandler().setEnableThreadDispatcher(true);

        // 模拟器的内存操作接口
        final Memory memory = emulator.getMemory();
        // 设置系统类库解析
        memory.setLibraryResolver(new AndroidResolver(23));
        // 创建Android虚拟机
        vm = emulator.createDalvikVM(new File("unidbg-android/src/test/resources/demo1/xhs.apk"));
        vm.setJni(this);
        vm.setVerbose(true);
        emulator.getSyscallHandler().addIOResolver(this);

        // memory.addModuleListener(new SearchRc4("libshield.so", 100000));

        DalvikModule dm = vm.loadLibrary("xyass", true);

        dm.callJNI_OnLoad(emulator);

        XhsHttpInterceptor = vm.resolveClass("com/xingin/shield/http/XhsHttpInterceptor");
        url = "https://edith.xiaohongshu.com/api/sns/v10/search/notes?keyword=%E9%85%92%E5%BA%97&filters=%5B%5D&sort=&page=1&page_size=20&source=explore_feed&search_id=29d5cm494nisivx4vhgcg&session_id=29d5clz8vjk88de721qtc&api_extra=&page_pos=0&pin_note_id=&allow_rewrite=1&geo=eyJsYXRpdHVkZSI6MC4wMDAwMDAsImxvbmdpdHVkZSI6MC4wMDAwMDB9%0A&word_request_id=&loaded_ad=&query_extra_info=&preview_ad=";

        request = new Request.Builder()
                .url(url)
                .addHeader("X-B3-TraceId", "4646d95f531b0700")
                .addHeader("xy-common-params", "fid=16378116221063de7cca58149ff002a512b5f111a91f&device_fingerprint=2021111516581947e1c4bc8239a2e1d42e744b1df706e501863445367d1d80&device_fingerprint1=2021111516581947e1c4bc8239a2e1d42e744b1df706e501863445367d1d80&launch_id=1637821982&tz=Asia%2FShanghai&channel=PMgdt20607990&versionName=7.7.0.1&deviceId=46a836d8-aebe-3453-88e9-a4c606a7df92&platform=android&sid=session.1637819616034278266051&identifier_flag=4&t=1637821993&project_id=ECFAAF&build=7070219&x_trace_page_current=search_result_notes&lang=zh-Hans&app_id=ECFAAF01&uis=light")
                .addHeader("User-Agent", "Dalvik/2.1.0 (Linux; U; Android 9; Redmi 6 Pro MIUI/V12.0.1.0.PDICNXM) Resolution/1080*2280 Version/7.7.0.1 Build/7070219 Device/(Xiaomi;Redmi 6 Pro) discover/7.7.0.1 NetType/WiFi")
                .build();
    }

    @Override
    public FileResult<?> resolve(Emulator emulator, String pathname, int oflags) {
        System.out.println("file open:"+pathname);
        if(pathname.contains("status")){
            return FileResult.success(new ByteArrayFileIO(oflags, pathname, "TracerPid:      0".getBytes(StandardCharsets.UTF_8)));
        }
        return null;
    }

    public static void main(String[] args) {
        long a = System.currentTimeMillis();
        XYASS xyass = new XYASS();
        xyass.callinitializeNative();
        xyass.callinitialize();
        xyass.callintercept();
    }

    // 第一个初始化函数
    public void callinitializeNative(){
        XhsHttpInterceptor.callStaticJniMethod(emulator, "initializeNative()V");
    };

    // 第一个初始化函数
    public void callinitialize(){
        xhshttpinterceptorObject = XhsHttpInterceptor.newObject("xhs");
        cPtr = xhshttpinterceptorObject.callJniMethodLong(emulator, "initialize(Ljava/lang/String;)J", "main");
    };

    // 目标函数
    public void callintercept(){
        DvmObject<?> chain = vm.resolveClass("okhttp3/Interceptor$Chain").newObject(null);
        xhshttpinterceptorObject.callJniMethodObject(emulator, "intercept(Lokhttp3/Interceptor$Chain;J)Lokhttp3/Response;", chain, cPtr);
        String shield = request.headers().get("shield");
        System.out.println("shield:"+shield);
        System.out.println(count);
    }

    @Override
    public DvmObject<?> getStaticObjectField(BaseVM vm, DvmClass dvmClass, String signature) {
        switch (signature){
            case "com/xingin/shield/http/ContextHolder->sLogger:Lcom/xingin/shield/http/ShieldLogger;":{
                return vm.resolveClass("com/xingin/shield/http/ShieldLogger").newObject(null);
            }
            case "com/xingin/shield/http/ContextHolder->sDeviceId:Ljava/lang/String;":{
                return new StringObject(vm, "1d41ebdc-86dd-33ea-9ceb-e9210babd74e");
            }
        }
        return super.getStaticObjectField(vm, dvmClass, signature);
    }


    @Override
    public void callVoidMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        switch (signature){
            case "com/xingin/shield/http/ShieldLogger->nativeInitializeStart()V":{
                return;
            }
            case "com/xingin/shield/http/ShieldLogger->nativeInitializeEnd()V":{
                return;
            }
            case "com/xingin/shield/http/ShieldLogger->initializeStart()V":{
                return;
            }
            case "com/xingin/shield/http/ShieldLogger->initializedEnd()V":{
                return;
            }
            case "com/xingin/shield/http/ShieldLogger->buildSourceStart()V":{
                return;
            }
            case "okhttp3/RequestBody->writeTo(Lokio/BufferedSink;)V":{
                BufferedSink bufferedSink = (BufferedSink) vaList.getObjectArg(0).getValue();
                RequestBody requestBody = (RequestBody) dvmObject.getValue();
                if(requestBody != null){
                    try {
                        requestBody.writeTo(bufferedSink);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                return;
            }
            case "com/xingin/shield/http/ShieldLogger->buildSourceEnd()V":{
                return;
            }
            case "com/xingin/shield/http/ShieldLogger->calculateStart()V":{
                return;
            }
            case "com/xingin/shield/http/ShieldLogger->calculateEnd()V":{
                return;
            }
        }
        super.callVoidMethodV(vm, dvmObject, signature, vaList);
    }

    @Override
    public DvmObject<?> callStaticObjectMethodV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
        switch (signature){
            case "java/nio/charset/Charset->defaultCharset()Ljava/nio/charset/Charset;":{
                return dvmClass.newObject(Charset.defaultCharset());
            }
            case "com/xingin/shield/http/Base64Helper->decode(Ljava/lang/String;)[B":{
                String input = vaList.getObjectArg(0).getValue().toString();
                return new ByteArray(vm, Base64.decodeBase64(input));
            }
        }
        return super.callStaticObjectMethodV(vm, dvmClass, signature, vaList);
    }


    @Override
    public int getIntField(BaseVM vm, DvmObject<?> dvmObject, String signature) {
        switch (signature){
            // 获取版本信息
            case "android/content/pm/PackageInfo->versionCode:I":{
                return 7070219;
            }
        }
        return super.getIntField(vm, dvmObject, signature);
    }

    @Override
    public int getStaticIntField(BaseVM vm, DvmClass dvmClass, String signature) {
        switch (signature){
            // 获取 sAppId
            case "com/xingin/shield/http/ContextHolder->sAppId:I":{
                return -319115519;
            }
        }
        return super.getStaticIntField(vm, dvmClass, signature);
    }

    @Override
    public DvmObject<?> callObjectMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        switch (signature){
            case "android/content/Context->getSharedPreferences(Ljava/lang/String;I)Landroid/content/SharedPreferences;":{
                return vm.resolveClass("android/content/SharedPreferences").newObject(vaList.getObjectArg(0).getValue().toString());
            }
            case "android/content/SharedPreferences->getString(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;":{
                String tableName = dvmObject.getValue().toString();
                // <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
                //<map>
                //    <string name="main_hmac">azQ8h5zsjnZeMgdjdyBb75mnlYEkBCTLhcrbzS569c3kaMugIEiGW2vu0Hua9XXSUr+NNObdo5nEshL/0Lyovz9OntdOkLyhI6dhIKrUCbYLdIicm+9JhFtM4rXiW9At</string>
                //</map>
                if(tableName.equals("s")){
                    String key = vaList.getObjectArg(0).getValue().toString();
                    System.out.println("get key:"+key);
                    switch (key){
                        case "main":{
                            return new StringObject(vm, "");
                        }
                        case "main_hmac":{
                            return new StringObject(vm, "azQ8h5zsjnZeMgdjdyBb75mnlYEkBCTLhcrbzS569c3kaMugIEiGW2vu0Hua9XXSUr+NNObdo5nEshL/0Lyovz9OntdOkLyhI6dhIKrUCbYLdIicm+9JhFtM4rXiW9At");
                        }
                    }
                }

            }
            case "okhttp3/Interceptor$Chain->request()Lokhttp3/Request;":{
                return vm.resolveClass("okhttp3/Request").newObject(request);
            }
            case "okhttp3/Request->url()Lokhttp3/HttpUrl;":{
                return vm.resolveClass("okhttp3/HttpUrl").newObject(request.url());
            }
            case "okhttp3/HttpUrl->encodedPath()Ljava/lang/String;":{
                HttpUrl httpUrl = (HttpUrl) dvmObject.getValue();
                return new StringObject(vm, httpUrl.encodedPath());
            }
            case "okhttp3/HttpUrl->encodedQuery()Ljava/lang/String;":{
                HttpUrl httpUrl = (HttpUrl) dvmObject.getValue();
                return new StringObject(vm, httpUrl.encodedQuery());
            }
            case "okhttp3/Request->body()Lokhttp3/RequestBody;":{
                return vm.resolveClass("okhttp3/RequestBody").newObject(request.body());
            }
            case "okhttp3/Request->headers()Lokhttp3/Headers;":{
                return vm.resolveClass("okhttp3/Headers").newObject(request.headers());
            }
            case "okio/Buffer->writeString(Ljava/lang/String;Ljava/nio/charset/Charset;)Lokio/Buffer;": {
                Buffer buffer = (Buffer) dvmObject.getValue();
                buffer.writeString(vaList.getObjectArg(0).getValue().toString(), (Charset) vaList.getObjectArg(1).getValue());
                return dvmObject;
            }
            case "okhttp3/Headers->name(I)Ljava/lang/String;":{
                Headers headers = (Headers) dvmObject.getValue();
                return new StringObject(vm, headers.name(vaList.getIntArg(0)));
            }
            case "okhttp3/Headers->value(I)Ljava/lang/String;":{
                Headers headers = (Headers) dvmObject.getValue();
                return new StringObject(vm, headers.value(vaList.getIntArg(0)));
            }
            case "okio/Buffer->clone()Lokio/Buffer;":{
                Buffer buffer = (Buffer) dvmObject.getValue();
                return vm.resolveClass("okio/Buffer").newObject(buffer.clone());
            }
            case "okhttp3/Request->newBuilder()Lokhttp3/Request$Builder;": {
                Request request = (Request) dvmObject.getValue();
                return vm.resolveClass("okhttp3/Request$Builder").newObject(request.newBuilder());
            }
            case "okhttp3/Request$Builder->header(Ljava/lang/String;Ljava/lang/String;)Lokhttp3/Request$Builder;": {
                Request.Builder builder = (Request.Builder) dvmObject.getValue();
                builder.header(vaList.getObjectArg(0).getValue().toString(), vaList.getObjectArg(1).getValue().toString());
                return dvmObject;
            }
            case "okhttp3/Request$Builder->build()Lokhttp3/Request;": {
                Request.Builder builder = (Request.Builder) dvmObject.getValue();
                request = builder.build();
                return vm.resolveClass("okhttp3/Request").newObject(request);
            }
            case "okhttp3/Interceptor$Chain->proceed(Lokhttp3/Request;)Lokhttp3/Response;": {
                return vm.resolveClass("okhttp3/Response").newObject(null);
            }
        }
        return super.callObjectMethodV(vm, dvmObject, signature, vaList);
    }

    @Override
    public DvmObject<?> newObjectV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
        switch (signature){
            case "okio/Buffer-><init>()V":{
                return dvmClass.newObject(new Buffer());
            }
        }
        return super.newObjectV(vm, dvmClass, signature, vaList);
    }

    @Override
    public int callIntMethodV(BaseVM vm, DvmObject<?> dvmObject, String signature, VaList vaList) {
        switch (signature){
            case "okhttp3/Headers->size()I":{
                Headers headers = (Headers) dvmObject.getValue();
                return headers.size();
            }
            case "okio/Buffer->read([B)I":{
                Buffer buffer = (Buffer) dvmObject.getValue();
                return buffer.read((byte[]) vaList.getObjectArg(0).getValue());
            }
            case "okhttp3/Response->code()I":
                return 200;
        }
        return super.callIntMethodV(vm, dvmObject, signature, vaList);
    }
}
